﻿Imports Framework.Data

Namespace TransferEngine
    Friend Class DownloadQueue
        Private Shared mSync As New Object

        'Private Shared mItemsById As New Generic.Dictionary(Of Integer, Entities.Downloads.Item)
        'Private Shared mFilesById As New Generic.Dictionary(Of Integer, Entities.Downloads.File)
        Private Shared mActivePartsByMessageId As New Generic.Dictionary(Of Integer, ActivePart)
        Private Shared mActivePartsByFileId As New Generic.Dictionary(Of Integer, Generic.List(Of ActivePart))

        Public Shared Sub Start()
            ReadFromDatabase()
            SetupEvents(True)
            ScanForChangedFiles()
            SetupFileSystemWatchers(True)
        End Sub
        Public Shared Sub [Stop]()
            SetupFileSystemWatchers(False)
            SetupEvents(False)
        End Sub

        Private Shared Sub SetupEvents(ByVal startListening As Boolean)
            If startListening Then
                AddHandler Framework.Net.Manager.NodeStatusChanged, AddressOf EventHandler_NodeStatusChanged
            Else
                RemoveHandler Framework.Net.Manager.NodeStatusChanged, AddressOf EventHandler_NodeStatusChanged
            End If
        End Sub

        Private Shared WithEvents mDownloadingWatcher As System.IO.FileSystemWatcher
        Private Shared WithEvents mCompletedWatcher As System.IO.FileSystemWatcher
        Private Shared WithEvents mRecycledWatcher As System.IO.FileSystemWatcher

        Private Shared Sub SetupFileSystemWatchers(ByVal start As Boolean)
            If Not start Then
                mDownloadingWatcher = Nothing
                mCompletedWatcher = Nothing
                mRecycledWatcher = Nothing
            Else
                'make sure root exists
                If String.IsNullOrEmpty(Settings.DownloadingFilesDirectory) Then Return

                If Not IO.Directory.Exists(Settings.DownloadingFilesDirectory) Then
                    Dim strText As String = String.Format("The download directory ""{0}"" does not exist or in not accessable", Settings.IncomingFilesDirectory)
                    Framework.Log.Provider.LogWarning(strText)
                    Framework.Win.MessageBox.ShowInfoDialog("FileShare - Buddy 2 Buddy", strText, 10)
                    Return
                End If

                mDownloadingWatcher = CreateDownloadFileSystemWatcher(Settings.DownloadingFilesDirectory)
                mCompletedWatcher = CreateDownloadFileSystemWatcher(Settings.CompleteFilesDirectory)
                mRecycledWatcher = CreateDownloadFileSystemWatcher(Settings.RecycledFilesDirectory)
            End If
        End Sub
        Private Shared Function CreateDownloadFileSystemWatcher(ByVal path As String) As System.IO.FileSystemWatcher
            If String.IsNullOrEmpty(path) Then Return Nothing

            If Not IO.Directory.Exists(path) Then IO.Directory.CreateDirectory(path)

            Dim clsRet As New System.IO.FileSystemWatcher(path)
            clsRet.IncludeSubdirectories = True
            clsRet.NotifyFilter = IO.NotifyFilters.DirectoryName Or IO.NotifyFilters.FileName Or IO.NotifyFilters.LastAccess Or IO.NotifyFilters.LastWrite
            clsRet.EnableRaisingEvents = True
            Return clsRet
        End Function

        Public Shared Function GetAllFiles() As Generic.List(Of Entities.Downloads.File)
            SyncLock mSync
                Return EntityHandlers.DownloadsFiles.GetAll
            End SyncLock
        End Function

        Public Shared Function IsFileActive(ByVal fileId As Integer) As Boolean
            SyncLock mSync
                Return mActivePartsByFileId.ContainsKey(fileId)
            End SyncLock
        End Function

        Private Shared Sub EventHandler_NodeStatusChanged(ByVal node As Framework.Net.Node)
            Try
                SyncLock mSync
                    AvailabilityTracker.NodeStatusChanged(node)
                    If Not node.IsOnline Then
                        For Each clsAP As ActivePart In mActivePartsByMessageId.Values.ToArray
                            If clsAP.Node.BuddyId = node.BuddyId Then
                                RemoveActivePart(clsAP)
                            End If
                        Next
                        For Each clsFile As Entities.Downloads.File In EntityHandlers.DownloadsFiles.GetAll
                            If clsFile.Status = Entities.Downloads.Status.Downloading AndAlso Not IsFileActive(clsFile.Id) Then
                                clsFile.Status = Entities.Downloads.Status.Waiting
                                EntityHandlers.DownloadsFiles.Save(clsFile)
                            End If
                        Next
                    End If
                End SyncLock
            Catch ex As Exception
                Framework.Misc.HandleException(ex)
            End Try
        End Sub


        Private Shared Sub ReadFromDatabase()
            SyncLock mSync
                For Each clsFile As Entities.Downloads.File In EntityHandlers.DownloadsFiles.GetAll
                    If clsFile.Status = Entities.Downloads.Status.Downloading Then
                        clsFile.Status = Entities.Downloads.Status.Waiting
                        EntityHandlers.DownloadsFiles.Save(clsFile)
                    End If
                Next
            End SyncLock
        End Sub

        Public Shared Sub RemoveAvailability(ByVal node As Framework.Net.Node, ByVal fileSha512Hash As String, ByVal fileSize As Long, ByVal partId As Integer)
            SyncLock mSync
                AvailabilityTracker.RemoveAvailability(node, fileSha512Hash, fileSize, partId)
            End SyncLock
        End Sub

        Public Shared Sub AddNewItems(ByVal item As Entities.Downloads.Item, ByVal file As Entities.Downloads.File)
            SyncLock mSync
                Dim aTmp As New Generic.List(Of Entities.Downloads.File)
                aTmp.Add(file)
                AddNewItems(item, aTmp)
            End SyncLock
        End Sub

        Public Shared Sub AddNewItems(ByVal item As Entities.Downloads.Item, ByVal files As Generic.List(Of Entities.Downloads.File))
            SyncLock mSync
                AvailabilityTracker.GetFileAvailabilities(files)
            End SyncLock
        End Sub

        Public Shared Function AnyIncompleteFilesForItem(ByVal itemId As Integer) As Boolean
            SyncLock mSync
                For Each clsFile As Entities.Downloads.File In EntityHandlers.DownloadsFiles.GetAllByItemId(itemId)
                    If clsFile.PartCount <> clsFile.PartsCompleted Then Return True
                Next
                Return False
            End SyncLock
        End Function

        Public Shared Function AddAsActivePart(ByVal part As PartRequest, ByVal requestMessageId As Integer) As Entities.Downloads.File
            SyncLock mSync
                Dim clsAP As New ActivePart
                clsAP.Node = part.Node
                clsAP.RequestedAt = Now
                clsAP.RequestMessageId = requestMessageId
                clsAP.PartId = part.PartId
                clsAP.FileId = part.FileId
                clsAP.TrackingId = part.TrackingId
                mActivePartsByMessageId.Add(requestMessageId, clsAP)
                SyncLock mActivePartsByFileId
                    Dim aAPL As Generic.List(Of ActivePart)
                    If mActivePartsByFileId.ContainsKey(part.FileId) Then
                        aAPL = mActivePartsByFileId(part.FileId)
                    Else
                        aAPL = New Generic.List(Of ActivePart)
                        mActivePartsByFileId.Add(part.FileId, aAPL)
                    End If
                    aAPL.Add(clsAP)
                End SyncLock
                Return EntityHandlers.DownloadsFiles.GetById(part.FileId)
            End SyncLock
        End Function
        Friend Shared Sub RemoveActivePart(ByVal part As ActivePart)
            SyncLock mSync
                mActivePartsByMessageId.Remove(part.RequestMessageId)
                If mActivePartsByFileId.ContainsKey(part.FileId) Then
                    Dim aAPL As Generic.List(Of ActivePart) = mActivePartsByFileId.Item(part.FileId)
                    If aAPL.Count = 1 Then
                        mActivePartsByFileId.Remove(part.FileId)
                    Else
                        aAPL.Remove(part)
                    End If
                End If
            End SyncLock
        End Sub
        Public Shared Function GetAllActiveParts() As ActivePart()
            SyncLock mSync
                Return mActivePartsByMessageId.Values.ToArray
            End SyncLock
        End Function
        Public Shared Sub ActivePartFinished(ByVal part As ActivePart)
            SyncLock mSync
                Dim clsFile As Entities.Downloads.File = EntityHandlers.DownloadsFiles.GetById(part.FileId)
                Dim clsItem As Entities.Downloads.Item = EntityHandlers.DownloadsItems.GetById(clsFile.ItemId)
                RemoveActivePart(part)
                Using t As New TransactionScope
                    t.SetInstanceFieldValue(clsFile, "PartsCompleted", clsFile.PartsCompleted + 1)
                    If clsFile.PartsCompleted = clsFile.PartCount Then
                        t.SetInstanceFieldValue(clsFile, "Completed", Now.ToUniversalTime)
                        t.SetInstanceFieldValue(clsFile, "Status", Entities.Downloads.Status.PendingComplete)
                        Dim dtLastChanged As Date
                        dtLastChanged = IO.File.GetLastWriteTimeUtc(IO.Path.Combine(clsFile.Directory, clsFile.Name))
                        t.SetInstanceFieldValue(clsFile, "LastChanged", dtLastChanged)
                        clsFile.PartStatuses = Nothing
                        Handler_ADS.UpdateADSMetaData(clsItem, clsFile, True)
                    Else
                        clsFile.PartStatuses(part.PartId) = Entities.Downloads.PartStatus.Complete
                    End If
                    EntityHandlers.DownloadsFiles.Save(clsFile, t)
                    t.Complete()
                End Using

                'now, if the all files in the item is complete, move the item's files to the complete directory
                Try
                    If clsFile.Status = Entities.Downloads.Status.PendingComplete Then
                        Dim aFiles As Generic.List(Of Entities.Downloads.File)
                        aFiles = EntityHandlers.DownloadsFiles.GetAllByItemId(clsItem.Id)

                        Dim blnAllComplete As Boolean = True
                        For Each clsItemFile As Entities.Downloads.File In aFiles
                            If clsItemFile.PartsCompleted <> clsItemFile.PartCount Then
                                blnAllComplete = False
                                Exit For
                            End If
                        Next
                        If blnAllComplete Then
                            Dim strTargetDir As String = IO.Path.Combine(Settings.CompleteFilesDirectory, clsItem.Name)
                            Dim strDirToMove As String = clsFile.Directory
                            If Not IO.Directory.Exists(IO.Path.GetDirectoryName(strTargetDir)) Then
                                IO.Directory.CreateDirectory(IO.Path.GetDirectoryName(strTargetDir))
                            End If
                            IO.Directory.Move(strDirToMove, strTargetDir)
                        End If
                    End If
                Catch ex As Exception
                    FileShare.Engine.Manager.OnLogEvent("Error moving completed items and files from downloading directory to complete directory", ex)
                End Try

                Try
                    Dim aSubscriptions As Generic.List(Of Entities.Downloads.FileSubscription)
                    aSubscriptions = EntityHandlers.DownloadsFileSubscriptions.GetBySHA512HashAndSize(clsFile.SHA512Hash, clsFile.Size)
                    If aSubscriptions IsNot Nothing Then
                        For Each clsSubscription As Entities.Downloads.FileSubscription In aSubscriptions
                            Dim clsNode As Framework.Net.Node
                            clsNode = Framework.Net.Manager.GetOnlineByBuddyId(clsSubscription.BuddyId)
                            If clsNode IsNot Nothing Then
                                Try
                                    'only send notification is buddy does not already have this part
                                    If Not AvailabilityTracker.IsPartAvailableAtNode(clsNode, clsFile, part.PartId) Then
                                        Commands.NewAvailablePartsNotification.SendNotificaction(clsNode, clsFile, part.PartId)
                                    End If
                                Catch ex As Exception
                                    FileShare.Engine.Manager.OnLogEvent(String.Format("Error sending new part notification to buddy {0}", clsNode.Name), ex)
                                    Framework.Misc.HandleException(ex)
                                End Try
                            End If
                        Next
                    End If
                Catch ex As Exception
                    FileShare.Engine.Manager.OnLogEvent("Error sending new part notification to buddies", ex)
                End Try
            End SyncLock
        End Sub

        Public Shared Function FindActivePartByMessageId(ByVal messageId As Integer) As ActivePart
            SyncLock mSync
                If mActivePartsByMessageId.ContainsKey(messageId) Then
                    Return mActivePartsByMessageId(messageId)
                Else
                    Return Nothing
                End If
            End SyncLock
        End Function

        Public Shared Function GetNextPartToDownload() As PartRequest
            'ok, so first we need to check if we are connected to any client 
            'which we're not already downling a part from
            SyncLock mSync

                Dim aFreeNodes As New Generic.Dictionary(Of Integer, Framework.Net.Node)
                For Each clsNode As Framework.Net.Node In Framework.Net.Manager.GetAllOnline
                    Dim blnIsBusy As Boolean = False
                    For Each clsPart As ActivePart In mActivePartsByMessageId.Values.ToArray
                        If clsPart.Node Is clsNode Then
                            blnIsBusy = True
                            Exit For
                        End If
                    Next
                    If Not blnIsBusy Then aFreeNodes.Add(clsNode.Buddy.Id, clsNode)
                Next
                'now, check if there are any free slots
                If aFreeNodes.Count = 0 Then Return Nothing

                'ok, now we know what clients are open for a request
                'so, now we gotta figure out what file to request parts for... So, in order of
                'priority and queuetime, sort the files we're downloading that aren't complete
                'by prio and queuedsince
                'at the same time, we only add files to the list that we know that the clients
                'with open slots has parts of
                Dim aFilesSorted As New Generic.SortedList(Of String, Entities.Downloads.File)
                For Each clsItem As Entities.Downloads.Item In EntityHandlers.DownloadsItems.GetAll
                    Dim sItemPrio As String = CInt(clsItem.Priority).ToString
                    Dim sId As String = clsItem.Id.ToString("0000000000")
                    For Each clsFile As Entities.Downloads.File In EntityHandlers.DownloadsFiles.GetAllByItemId(clsItem.Id)
                        Select Case clsFile.Status
                            Case Entities.Downloads.Status.Downloading, Entities.Downloads.Status.Waiting
                                'ok, so the file isn't complete. But does any of the free clients
                                'have any parts at all of this file?
                                If AvailabilityTracker.AnyAvailablePartsOfFileAtOneOfTheNodes(aFreeNodes.Values.ToArray, clsFile) Then
                                    'whee, this file is a candidate for downloading a part of
                                    'so add it to the list
                                    Dim sFilePrio As String = CInt(clsFile.Priority).ToString
                                    Dim sFileId As String = clsFile.Id.ToString
                                    Dim strKey As String = String.Format("{0}_{1}_{2}_{3}", sItemPrio, sId, sFilePrio, sFileId)
                                    aFilesSorted.Add(strKey, clsFile)
                                End If
                        End Select
                    Next
                Next

                'ok, so far so good. We now know the files, sorted in order of priority, that also has
                'a free client that can provide us parts of the file
                'so, now we start, in priority order, to try and find a client that has parts that
                'we don't. And, if many clients can provide part to a file, we download any given part
                'from the clients with the least completed file. If we're in a mesh (where both me
                'and the client with the imcomplete file know the same complete source, this is how we
                'try to get the part distribution to utilize available bandwith optimally)
                For Each clsFile As Entities.Downloads.File In aFilesSorted.Values
                    'now, any parts left that we need to start downloading?
                    '(a file can be marked as incomplete, but have the parts missing
                    'in transit)
                    Dim blnAnyNeededPart As Boolean = False
                    If clsFile.PartStatuses IsNot Nothing Then
                        For i As Integer = 0 To clsFile.PartStatuses.Count - 1
                            If clsFile.PartStatuses(i) = Entities.Downloads.PartStatus.Missing Then
                                blnAnyNeededPart = True
                                Exit For
                            End If
                        Next
                    End If
                    If blnAnyNeededPart Then
                        'guess we need more of this file
                        'now, find the least distributed part that we still require 
                        'among the list of free clients 
                        Dim clsPart As PartRequest = AvailabilityTracker.FindLeastDistributedPartOfFile(aFreeNodes.Values.ToArray, clsFile)
                        If clsPart IsNot Nothing Then
                            Return clsPart
                        End If
                    End If
                Next
                Return Nothing
            End SyncLock
        End Function

        Public Shared Sub PauseDownloading(ByVal files As Generic.List(Of Entities.Downloads.File))
            SyncLock mSync
                If files IsNot Nothing Then
                    For Each clsFile As Entities.Downloads.File In files
                        If mActivePartsByFileId.ContainsKey(clsFile.Id) Then
                            Dim aAPL() As ActivePart = mActivePartsByFileId(clsFile.Id).ToArray
                            For Each clsAP As ActivePart In aAPL
                                RemoveActivePart(clsAP)
                                Commands.CancelFilePartRequest.SendRequest(clsAP.Node, clsAP.TrackingId)
                            Next
                        End If
                        clsFile.Status = Entities.Downloads.Status.Paused
                        EntityHandlers.DownloadsFiles.Save(clsFile)
                    Next
                End If
            End SyncLock
        End Sub
        Public Shared Sub ResumeDownloading(ByVal files As Generic.List(Of Entities.Downloads.File))
            SyncLock mSync
                If files IsNot Nothing Then
                    For Each clsFile As Entities.Downloads.File In files
                        clsFile.Status = Entities.Downloads.Status.Waiting
                        EntityHandlers.DownloadsFiles.Save(clsFile)
                    Next
                End If
            End SyncLock
        End Sub
        Public Shared Sub RecycleDownloads(ByVal files As Generic.List(Of Entities.Downloads.File))
            RecycleAndDeleteDownloadsWorker(False, files)
        End Sub
        Public Shared Sub DeleteDownloads(ByVal files As Generic.List(Of Entities.Downloads.File))
            RecycleAndDeleteDownloadsWorker(True, files)
        End Sub
        Private Shared Sub RecycleAndDeleteDownloadsWorker(ByVal isDelete As Boolean, ByVal files As Generic.List(Of Entities.Downloads.File))
            SyncLock mSync
                If files IsNot Nothing Then
                    For Each clsFile As Entities.Downloads.File In files
                        If mActivePartsByFileId.ContainsKey(clsFile.Id) Then
                            Dim aAPL() As ActivePart = mActivePartsByFileId(clsFile.Id).ToArray
                            For Each clsAP As ActivePart In aAPL
                                RemoveActivePart(clsAP)
                                Commands.CancelFilePartRequest.SendRequest(clsAP.Node, clsAP.TrackingId)
                            Next
                        End If
                    Next
                End If

                Dim aItemsDeleted As New Generic.List(Of Entities.Downloads.Item)

                Using t As New TransactionScope
                    For Each clsFile As Entities.Downloads.File In files
                        Dim clsItem As Entities.Downloads.Item = EntityHandlers.DownloadsItems.GetById(clsFile.ItemId)
                        Dim strSourceDir As String = clsFile.Directory
                        Dim strSourceFilePath As String = IO.Path.Combine(strSourceDir, clsFile.Name)
                        If Not IO.File.Exists(strSourceFilePath) Then
                            EntityHandlers.DownloadsFiles.Delete(clsFile.Id, t)
                            If EntityHandlers.DownloadsFiles.GetAllByItemId(clsItem.Id, t).Count = 0 Then
                                EntityHandlers.DownloadsItems.Delete(clsItem.Id, t)
                                aItemsDeleted.Add(clsItem)
                            End If
                        ElseIf isDelete Then 'If mode = RecycleDeleteWorkerMode.Delete Then 'just remove file
                            IO.File.Delete(strSourceFilePath)
                        Else 'recycle
                            Dim strTargetDir As String = IO.Path.Combine(Settings.RecycledFilesDirectory, clsItem.Name)
                            If Not IO.Directory.Exists(strTargetDir) Then IO.Directory.CreateDirectory(strTargetDir)
                            Dim strDestFilePath As String = IO.Path.Combine(strTargetDir, clsFile.Name)
                            IO.File.Move(strSourceFilePath, strDestFilePath)
                        End If

                        'if source file's directory is empty, delete it
                        Dim aFIs As Generic.List(Of IO.FileInfo)
                        aFIs = FileSystemScan.ScanFolder_Common.GetAllFileInfosRecursive(strSourceDir).Values.ToList
                        If aFIs.Count = 0 Then
                            If IO.Directory.Exists(strSourceDir) Then IO.Directory.Delete(strSourceDir)
                        End If
                    Next
                    t.Complete()
                End Using
            End SyncLock
        End Sub

        Friend Shared Sub AddNewAvailablePart(ByVal node As Framework.Net.Node, ByVal fileSHA512 As String, ByVal fileSize As Long, ByVal partId As Integer)
            SyncLock mSync
                AvailabilityTracker.AddNewAvailablePart(node, fileSHA512, fileSize, partId)
            End SyncLock
        End Sub

        Private Class AvailabilityTracker
            Private Shared mNodes As New Generic.Dictionary(Of Integer, Generic.List(Of FileAvailablitySummary))

            Friend Shared Sub NodeStatusChanged(ByVal node As Framework.Net.Node)
                If node.IsOnline Then
                    RefreshNodeFileAvailabilities(node)
                Else
                    If node.Buddy IsNot Nothing AndAlso mNodes.ContainsKey(node.Buddy.Id) Then mNodes.Remove(node.Buddy.Id)
                End If
            End Sub

            Public Shared Function AnyAvailablePartsOfFileAtOneOfTheNodes(ByVal clients() As Framework.Net.Node, ByVal file As Entities.Downloads.File) As Boolean
                For Each clsNode As Framework.Net.Node In clients
                    If mNodes.ContainsKey(clsNode.Buddy.Id) Then
                        Dim aFAs As Generic.List(Of FileAvailablitySummary) = mNodes.Item(clsNode.Buddy.Id)
                        If aFAs IsNot Nothing AndAlso aFAs.Count > 0 Then
                            For Each clsFS As FileAvailablitySummary In aFAs
                                If clsFS.SHA512Hash = file.SHA512Hash AndAlso clsFS.Size = file.Size Then
                                    Return True
                                End If
                            Next
                        End If
                    End If
                Next
                Return False
            End Function
            Public Shared Function IsPartAvailableAtNode(ByVal node As Framework.Net.Node, ByVal file As Entities.Downloads.File, ByVal partId As Integer) As Boolean
                If Not mNodes.ContainsKey(node.BuddyId) Then Return False
                Dim aFAs As Generic.List(Of FileAvailablitySummary) = mNodes.Item(node.BuddyId)
                For Each clsFA As FileAvailablitySummary In aFAs
                    If clsFA.Size = file.Size AndAlso clsFA.SHA512Hash = file.SHA512Hash Then
                        Return IsPartAvailable(partId, clsFA)
                    End If
                Next
                Return False
            End Function

            Public Shared Sub RemoveAvailability(ByVal node As Framework.Net.Node, ByVal fileSha512Hash As String, ByVal fileSize As Long, ByVal partId As Integer)
                If Not mNodes.ContainsKey(node.BuddyId) Then Return
                Dim aFAs As Generic.List(Of FileAvailablitySummary) = mNodes.Item(node.BuddyId)
                For Each clsFA As FileAvailablitySummary In aFAs
                    If clsFA.Size = fileSize AndAlso clsFA.SHA512Hash = fileSha512Hash Then
                        clsFA.Parts(partId) = 0
                        Exit For
                    End If
                Next
            End Sub

            Public Shared Function FindLeastDistributedPartOfFile(ByVal freeNodes() As Framework.Net.Node, ByVal file As Entities.Downloads.File) As PartRequest
                Dim aPartsHere As New Generic.Dictionary(Of Integer, Boolean)
                For iPartId As Integer = 0 To file.PartStatuses.Count - 1
                    If file.PartStatuses(iPartId) <> Entities.Downloads.PartStatus.Missing Then
                        aPartsHere.Add(iPartId, True)
                    End If
                Next

                Dim iPartIdToRequest As Integer = -1
                Dim clsNodeToAsk As Framework.Net.Node = Nothing

                'try to find a part with just 1 source, loop until we reach the number of available clients - 1
                Dim aFAs As Generic.List(Of FileAvailablitySummary)
                aFAs = GetAllAvailablitySummariesForFile(file)

                For iTargetCount As Integer = 1 To aFAs.Count
                    Dim iPartId As Integer = CInt(Rnd() * file.PartCount)
                    For iPartIdIndex As Integer = 0 To file.PartCount - 1
                        iPartId += 1
                        If iPartId >= file.PartCount Then iPartId = 0

                        If Not aPartsHere.ContainsKey(iPartId) Then
                            Dim iPartCount As Integer = 0
                            For Each clsFA As FileAvailablitySummary In aFAs
                                If IsPartAvailable(iPartId, clsFA) Then iPartCount += 1
                            Next
                            If iPartCount = iTargetCount Then
                                'ok, so we now now that this part is the one with the lowest
                                'availability in ALL our buddies. But, is it available at
                                'one of the free clients?
                                Dim clsAvailableAtNode As Framework.Net.Node
                                clsAvailableAtNode = FindNodeWithPartAvailable(file, iPartId, freeNodes)
                                If clsAvailableAtNode IsNot Nothing Then
                                    clsNodeToAsk = clsAvailableAtNode
                                    iPartIdToRequest = iPartId
                                    Exit For
                                End If
                            End If
                        End If
                    Next
                    If iPartIdToRequest >= 0 Then Exit For
                Next

                If iPartIdToRequest >= 0 Then
                    Dim clsRet As New PartRequest
                    clsRet.Node = clsNodeToAsk
                    clsRet.PartId = iPartIdToRequest
                    clsRet.FileSHA512 = file.SHA512Hash
                    clsRet.Size = file.Size
                    clsRet.FileId = file.Id
                    clsRet.TrackingId = String.Format("FS.DLF.{0}", file.Id)
                    Return clsRet
                Else
                    Return Nothing
                End If
            End Function

            Private Shared Function FindNodeWithPartAvailable(ByVal file As Entities.Downloads.File, ByVal partId As Integer, ByVal clients() As Framework.Net.Node) As Framework.Net.Node
                Dim aFAs As Generic.List(Of FileAvailablitySummary) = GetAllAvailablitySummariesForFile(file)

                For Each clsNode As Framework.Net.Node In clients
                    For Each clsFA As FileAvailablitySummary In aFAs
                        If clsFA.AtBuddyId = clsNode.Buddy.Id Then
                            Return clsNode
                        End If
                    Next
                Next
                Return Nothing
            End Function

            Private Shared Function GetAllAvailablitySummariesForFile(ByVal file As Entities.Downloads.File) As Generic.List(Of FileAvailablitySummary)
                Dim aRet As New Generic.List(Of FileAvailablitySummary)
                For Each aFAs As Generic.List(Of FileAvailablitySummary) In mNodes.Values.ToArray
                    For Each clsFA As FileAvailablitySummary In aFAs
                        If clsFA.Size = file.Size AndAlso clsFA.SHA512Hash = file.SHA512Hash Then
                            aRet.Add(clsFA)
                            Exit For
                        End If
                    Next
                Next
                Return aRet
            End Function

            Private Shared Function IsPartAvailable(ByVal partId As Integer, ByVal fileAvailabilitySummary As FileAvailablitySummary) As Boolean
                Return fileAvailabilitySummary.Parts(partId) = 1
            End Function

            Private Shared Function FindIncompleteFile(ByVal file As Entities.Downloads.File, ByVal fileAvailabilitySummaries As Generic.List(Of FileAvailablitySummary)) As FileAvailablitySummary
                For Each clsFS As FileAvailablitySummary In fileAvailabilitySummaries
                    If clsFS.Size = file.Size AndAlso clsFS.SHA512Hash = file.SHA512Hash Then
                        For i As Integer = 0 To clsFS.Parts.Count - 1
                            If clsFS.Parts(i) = 0 Then Return clsFS
                        Next
                    End If
                Next
                Return Nothing
            End Function


            Private Shared Function GetPartsAtNode(ByVal node As Framework.Net.Node, ByVal file As Entities.Downloads.File) As Byte()
                Dim aFAs As Generic.List(Of FileAvailablitySummary)
                aFAs = GetNodeFileAvailablitySummaries(node)
                If aFAs IsNot Nothing AndAlso aFAs.Count > 0 Then
                    For Each clsFA As FileAvailablitySummary In aFAs
                        If clsFA.SHA512Hash = file.SHA512Hash AndAlso clsFA.Size = file.Size Then
                            Return clsFA.Parts
                        End If
                    Next
                End If
                Return Nothing
            End Function

            Private Shared Function GetNodeFileAvailablitySummaries(ByVal node As Framework.Net.Node) As Generic.List(Of FileAvailablitySummary)
                SyncLock mNodes
                    If mNodes.ContainsKey(node.Buddy.Id) Then
                        Return mNodes.Item(node.Buddy.Id)
                    Else
                        Return Nothing
                    End If
                End SyncLock
            End Function


            Private Shared Sub RefreshNodeFileAvailabilities(ByVal node As Framework.Net.Node)
                'build array of file signatures
                Dim aFileSigs As New Generic.List(Of Commands.GetAvailableParts.Request.FileSignature)
                For Each clsFile As Entities.Downloads.File In EntityHandlers.DownloadsFiles.GetAll
                    Select Case clsFile.Status
                        Case Entities.Downloads.Status.Downloading, Entities.Downloads.Status.Waiting, Entities.Downloads.Status.Paused
                            Dim clsFileSig As New Commands.GetAvailableParts.Request.FileSignature
                            clsFileSig.Size = clsFile.Size
                            clsFileSig.SHA512Hash = clsFile.SHA512Hash
                            aFileSigs.Add(clsFileSig)
                    End Select
                Next
                'if any uncompleted files were found, ask every client for availability
                'for all files we need part of
                'we also send the request if no files needs to be downloaded. This is to
                'update the buddy's file subscription table
                Commands.GetAvailableParts.SendRequest(node, aFileSigs, True, AddressOf GetAvailabilityReply, False)
            End Sub
            Public Shared Sub GetFileAvailabilities(ByVal files As Generic.List(Of Entities.Downloads.File))
                'build array of file signatures
                Dim aFileSigs As New Generic.List(Of Commands.GetAvailableParts.Request.FileSignature)
                For Each clsFile As Entities.Downloads.File In EntityHandlers.DownloadsFiles.GetAll
                    Select Case clsFile.Status
                        Case Entities.Downloads.Status.Downloading, Entities.Downloads.Status.Waiting, Entities.Downloads.Status.Paused
                            Dim clsFileSig As New Commands.GetAvailableParts.Request.FileSignature
                            clsFileSig.Size = clsFile.Size
                            clsFileSig.SHA512Hash = clsFile.SHA512Hash
                            aFileSigs.Add(clsFileSig)
                    End Select
                Next
                'if any uncompleted files were found, ask every client for availability
                'for all files we need part of
                If aFileSigs.Count > 0 Then
                    For Each clsNode As Framework.Net.Node In Framework.Net.Manager.GetAllOnline
                        Try
                            Commands.GetAvailableParts.SendRequest(clsNode, aFileSigs, False, AddressOf GetAvailabilityReply, False)
                        Catch ex As Exception
                            Framework.Misc.HandleException(ex)
                        End Try
                    Next
                End If
            End Sub

            Private Shared Sub GetAvailabilityReply(ByVal inReplyToMessageId As Integer, ByVal node As Framework.Net.Node, ByVal reply As Commands.GetAvailableParts.Reply)
                Try
                    AddNodeResponse(node, reply.AvailableParts)
                Catch ex As Exception
                    Framework.Misc.HandleException(ex)
                End Try
            End Sub

            Private Shared Sub AddNodeResponse(ByVal node As Framework.Net.Node, ByVal availability As Generic.List(Of FileAvailablitySummary))
                SyncLock mNodes
                    If mNodes.ContainsKey(node.Buddy.Id) Then mNodes.Remove(node.Buddy.Id)
                    mNodes.Add(node.Buddy.Id, availability)
                End SyncLock
            End Sub
            Public Shared Sub AddNewAvailablePart(ByVal node As Framework.Net.Node, ByVal fileSHA512Hash As String, ByVal fileSize As Long, ByVal partId As Integer)
                Dim clsAV As FileAvailablitySummary = Nothing
                SyncLock mNodes
                    Dim aAVs As Generic.List(Of FileAvailablitySummary)
                    If mNodes.ContainsKey(node.BuddyId) Then
                        aAVs = New Generic.List(Of FileAvailablitySummary)
                        mNodes.Add(node.BuddyId, aAVs)
                    Else
                        aAVs = mNodes(node.BuddyId)
                    End If

                    For Each clsTmpAV As FileAvailablitySummary In aAVs
                        If clsTmpAV.Size = fileSize AndAlso clsTmpAV.SHA512Hash = fileSHA512Hash Then
                            clsAV = clsTmpAV
                            Exit For
                        End If
                    Next
                    If clsAV Is Nothing Then
                        clsAV = New FileAvailablitySummary
                        clsAV.AtBuddyId = node.Buddy.Id
                        clsAV.SHA512Hash = fileSHA512Hash
                        clsAV.Size = fileSize
                        clsAV.Parts = TransferEngine.Manager.CreatePartStatusArray(fileSize, Entities.Downloads.PartStatus.Missing)
                        aAVs.Add(clsAV)
                    End If
                    clsAV.Parts(partId) = Entities.Downloads.PartStatus.Complete
                End SyncLock
            End Sub
        End Class

        Private Shared Sub mCompletedWatcher_Created(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs) Handles mCompletedWatcher.Created
            Try
                FileShare.Engine.Manager.OnFileSysWatcherTrace("DownloadsCompleted", "Created", e.FullPath, False)
                SetFilesStatus(Entities.Downloads.Status.Complete, e.FullPath)
            Catch ex As Exception
                FileShare.Engine.Manager.OnLogEvent("Error " & ex.Message, ex)
            End Try
        End Sub

        Private Shared Sub mCompletedWatcher_Deleted(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs) Handles mCompletedWatcher.Deleted
            Try
                FileShare.Engine.Manager.OnFileSysWatcherTrace("DownloadsCompleted", "Deleted", e.FullPath, False)
                SetFilesStatus(Entities.Downloads.Status.Deleted, e.FullPath)
            Catch ex As Exception
                Framework.Misc.HandleException(ex)
            End Try
        End Sub

        Private Shared Sub SetFilesStatus(ByVal newStatus As Entities.Downloads.Status, ByVal rootPath As String)
            SyncLock mSync
                'try to get all files by directory (assume rootPath points/pointed to a directory
                Dim aFiles As Generic.List(Of Entities.Downloads.File)
                aFiles = EntityHandlers.DownloadsFiles.GetAllByDirectoryRecursive(rootPath)
                If aFiles.Count = 0 Then
                    Dim strDir As String = IO.Path.GetDirectoryName(rootPath)
                    Dim strName As String = IO.Path.GetFileName(rootPath)
                    'rootPath probably pointed/points to a file
                    Dim clsFile As Entities.Downloads.File = EntityHandlers.DownloadsFiles.GetByDirectoryAndFilename(strDir, strName)
                    If clsFile IsNot Nothing Then aFiles.Add(clsFile)
                End If
                If aFiles.Count = 0 Then
                    'ok, now try to find the ADSMetaData
                    Dim aFIs As New Generic.List(Of IO.FileInfo)
                    If IO.Directory.Exists(rootPath) Then
                        aFIs = FileSystemScan.ScanFolder_Common.GetAllFileInfosRecursive(rootPath).Values.ToList
                    ElseIf IO.File.Exists(rootPath) Then
                        aFIs.Add(New IO.FileInfo(rootPath))
                    End If

                    If aFIs.Count > 0 Then
                        For Each clsFI As IO.FileInfo In aFIs
                            Dim clsMD As ADSMetaData = Handler_ADS.ReadADSMetaData(clsFI.FullName)
                            Dim clsFile As Entities.Downloads.File = Nothing
                            If clsMD IsNot Nothing Then
                                clsFile = EntityHandlers.DownloadsFiles.GetById(clsMD.FileId)
                            Else
                                clsFile = EntityHandlers.DownloadsFiles.GetByNameSizeAndLastChanged(clsFI.Name, clsFI.Length, clsFI.LastWriteTimeUtc)
                            End If
                            If clsFile IsNot Nothing Then
                                clsFile.Directory = clsFI.DirectoryName
                                clsFile.Name = clsFI.Name
                                aFiles.Add(clsFile)
                            End If
                        Next
                    End If
                End If
                If aFiles.Count > 0 Then
                    Using t As New TransactionScope
                        For Each clsFile As Entities.Downloads.File In aFiles
                            clsFile.Status = newStatus
                            EntityHandlers.DownloadsFiles.Save(clsFile, t)
                        Next
                        t.Complete()
                    End Using
                End If
            End SyncLock
        End Sub

        Private Shared Sub mCompletedWatcher_Renamed(ByVal sender As Object, ByVal e As System.IO.RenamedEventArgs) Handles mCompletedWatcher.Renamed
            Try
                FileShare.Engine.Manager.OnFileSysWatcherTrace("DownloadsCompleted", "Renamed", String.Format("{0} to {1}", e.OldFullPath, e.FullPath), False)
                RenameFile(e.OldFullPath, e.FullPath)
            Catch ex As Exception
                FileShare.Engine.Manager.OnLogEvent("Error " & ex.Message, ex)
            End Try
        End Sub

        Private Shared Sub mRecycledWatcher_Created(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs) Handles mRecycledWatcher.Created
            Try
                FileShare.Engine.Manager.OnFileSysWatcherTrace("DownloadsRecycled", "Created", e.FullPath, False)
                SetFilesStatus(Entities.Downloads.Status.Recycled, e.FullPath)
            Catch ex As Exception
                FileShare.Engine.Manager.OnLogEvent("Error " & ex.Message, ex)
            End Try
        End Sub

        Private Shared Sub mRecycledWatcher_Deleted(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs) Handles mRecycledWatcher.Deleted
            Try
                FileShare.Engine.Manager.OnFileSysWatcherTrace("DownloadsRecycled", "Deleted", e.FullPath, False)
                SetFilesStatus(Entities.Downloads.Status.Deleted, e.FullPath)
            Catch ex As Exception
                FileShare.Engine.Manager.OnLogEvent("Error " & ex.Message, ex)
            End Try
        End Sub

        Private Shared Sub mRecycledWatcher_Renamed(ByVal sender As Object, ByVal e As System.IO.RenamedEventArgs) Handles mRecycledWatcher.Renamed
            Try
                FileShare.Engine.Manager.OnFileSysWatcherTrace("DownloadsRecycled", "Renamed", String.Format("{0} to {1}", e.OldFullPath, e.FullPath), False)
                RenameFile(e.OldFullPath, e.FullPath)
            Catch ex As Exception
                FileShare.Engine.Manager.OnLogEvent("Error " & ex.Message, ex)
            End Try
        End Sub
        Private Shared Sub RenameFile(ByVal oldPath As String, ByVal newPath As String)
            SyncLock mSync
                If IO.File.Exists(newPath) Then
                    Dim clsMD As ADSMetaData = Handler_ADS.ReadADSMetaData(newPath)
                    Dim clsFile As Entities.Downloads.File
                    If clsMD IsNot Nothing Then
                        clsFile = EntityHandlers.DownloadsFiles.GetById(clsMD.FileId)
                    Else
                        Dim strDir As String = IO.Path.GetDirectoryName(oldPath)
                        Dim strFile As String = IO.Path.GetFileName(oldPath)
                        clsFile = EntityHandlers.DownloadsFiles.GetByDirectoryAndFilename(strDir, strFile)
                    End If
                    If clsFile IsNot Nothing Then
                        clsFile.Directory = IO.Path.GetDirectoryName(newPath)
                        clsFile.Name = IO.Path.GetDirectoryName(oldPath)
                        EntityHandlers.DownloadsFiles.Save(clsFile)
                    End If
                End If
            End SyncLock
        End Sub

        Private Shared Sub mDownloadingWatcher_Created(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs) Handles mDownloadingWatcher.Created
            Try
            Catch ex As Exception
                FileShare.Engine.Manager.OnLogEvent("Error: " & ex.Message, ex)
            End Try
        End Sub

        Private Shared Sub mDownloadingWatcher_Deleted(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs) Handles mDownloadingWatcher.Deleted
            Try
                FileShare.Engine.Manager.OnFileSysWatcherTrace("DownloadsDownloading", "Deleted", e.FullPath, False)
                SetFilesStatus(Entities.Downloads.Status.Deleted, e.FullPath)
            Catch ex As Exception
                FileShare.Engine.Manager.OnLogEvent("Error: " & ex.Message, ex)
            End Try
        End Sub

        Private Shared Sub ScanForChangedFiles()
            'look in downloading first
            Dim aFIs As Generic.Dictionary(Of String, System.IO.FileInfo)
            Dim aFiles As Generic.List(Of Entities.Downloads.File)

            'first, scan the incoming/downloading directory for changes
            aFIs = FileSystemScan.ScanFolder_Common.GetAllFileInfosRecursive(Settings.DownloadingFilesDirectory)
            aFiles = EntityHandlers.DownloadsFiles.GetAllByStatus(Entities.Downloads.Status.Downloading)
            aFiles.AddRange(EntityHandlers.DownloadsFiles.GetAllByStatus(Entities.Downloads.Status.Paused))
            aFiles.AddRange(EntityHandlers.DownloadsFiles.GetAllByStatus(Entities.Downloads.Status.PendingComplete))
            aFiles.AddRange(EntityHandlers.DownloadsFiles.GetAllByStatus(Entities.Downloads.Status.Waiting))
            ScanForChangedFiles(aFIs, aFiles, Entities.Downloads.Status.Waiting)

            'now, we scan the completed folder for changes
            aFIs = FileSystemScan.ScanFolder_Common.GetAllFileInfosRecursive(Settings.CompleteFilesDirectory)
            aFiles = EntityHandlers.DownloadsFiles.GetAllByStatus(Entities.Downloads.Status.Complete)
            aFiles.AddRange(EntityHandlers.DownloadsFiles.GetAllByStatus(Entities.Downloads.Status.RehashCompleteFile))
            ScanForChangedFiles(aFIs, aFiles, Entities.Downloads.Status.Complete)

            'now, we scan the completed folder for changes
            aFIs = FileSystemScan.ScanFolder_Common.GetAllFileInfosRecursive(Settings.RecycledFilesDirectory)
            aFiles = EntityHandlers.DownloadsFiles.GetAllByStatus(Entities.Downloads.Status.Recycled)
            aFiles.AddRange(EntityHandlers.DownloadsFiles.GetAllByStatus(Entities.Downloads.Status.RehashRecycledFile))
            ScanForChangedFiles(aFIs, aFiles, Entities.Downloads.Status.Recycled)
        End Sub
        Private Shared Sub ScanForChangedFiles(ByVal diskFiles As Generic.Dictionary(Of String, System.IO.FileInfo), ByVal dbFiles As Generic.List(Of Entities.Downloads.File), ByVal statusForNewFilesFound As Entities.Downloads.Status)
            'first, check so that all dbfiles still exist on disk
            For Each clsFile As Entities.Downloads.File In dbFiles
                Dim strPath As String = IO.Path.Combine(clsFile.Directory, clsFile.Name).ToLower

                If diskFiles.ContainsKey(strPath) Then
                    Dim clsFI As System.IO.FileInfo = diskFiles(strPath)
                    diskFiles.Remove(strPath)

                    If Math.Abs(clsFile.LastChanged.Subtract(clsFI.LastWriteTimeUtc).TotalSeconds) > 1 Then
                        SyncLock mSync
                            'file has changed, what do we do now?
                            Select Case statusForNewFilesFound
                                Case Entities.Downloads.Status.Waiting
                                    'ok, so we're in the downloading path - reset and restart download of this file
                                    Using fs As New IO.FileStream(strPath, IO.FileMode.Create, IO.FileAccess.Write)
                                        fs.SetLength(clsFile.Size)
                                    End Using
                                    clsFile.PartsCompleted = 0
                                    clsFile.LastChanged = clsFI.LastWriteTimeUtc
                                    clsFile.PartStatuses = TransferEngine.Manager.CreatePartStatusArray(clsFile.Size, Entities.Downloads.PartStatus.Missing)
                                    EntityHandlers.DownloadsFiles.Save(clsFile)
                                Case Entities.Downloads.Status.Recycled
                                    clsFile.Status = Entities.Downloads.Status.RehashRecycledFile
                                    EntityHandlers.DownloadsFiles.Save(clsFile)
                                Case Entities.Downloads.Status.Complete
                                    clsFile.Status = Entities.Downloads.Status.RehashCompleteFile
                                    EntityHandlers.DownloadsFiles.Save(clsFile)
                                Case Else
                                    Throw New NotImplementedException
                            End Select
                        End SyncLock
                    End If
                Else
                    SyncLock mSync
                        Select Case statusForNewFilesFound
                            Case Entities.Downloads.Status.Waiting
                                clsFile.PartsCompleted = 0
                                clsFile.PartStatuses = TransferEngine.Manager.CreatePartStatusArray(clsFile.Size, Entities.Downloads.PartStatus.Missing)
                            Case Entities.Downloads.Status.Complete
                                clsFile.Status = Entities.Downloads.Status.Deleted
                            Case Entities.Downloads.Status.Recycled
                                clsFile.Status = Entities.Downloads.Status.Deleted
                        End Select
                        EntityHandlers.DownloadsFiles.Save(clsFile)
                    End SyncLock
                End If
            Next
            'now, for files on disk that we don't have in the db, create db-information for them
            For Each strPath As String In diskFiles.Keys
                Dim clsFI As System.IO.FileInfo = diskFiles(strPath)

                'is there ADS-metadata?
                Dim clsMD As ADSMetaData = Handler_ADS.GetADSMetaDataIfValid(clsFI)
                If clsMD IsNot Nothing Then
                    SyncLock mSync
                        Dim clsFile As Entities.Downloads.File
                        clsFile = EntityHandlers.DownloadsFiles.GetById(clsMD.FileId)
                        If clsFile Is Nothing Then
                            clsFile = New Entities.Downloads.File
                            clsFile.PartCount = TransferEngine.Manager.CalculatePartCountEx(clsFI.Length)
                            If clsMD.FileIsComplete Then
                                clsFile.PartStatuses = Nothing
                                clsFile.Completed = Now
                                clsFile.PartsCompleted = clsFile.PartCount
                            Else
                                If clsMD.FilePartStatuses IsNot Nothing AndAlso clsMD.FilePartStatuses.Count = Manager.CalculatePartCountEx(clsMD.FileSHA512FileSize) Then
                                    clsFile.PartStatuses = clsMD.FilePartStatuses
                                Else
                                    clsFile.PartStatuses = Manager.CreatePartStatusArray(clsFI.Length, Entities.Downloads.PartStatus.Missing)
                                End If
                                clsFile.Completed = DateTime.MinValue
                                clsFile.PartsCompleted = 0
                                For i As Integer = 0 To clsFile.PartStatuses.Count - 1
                                    If clsFile.PartStatuses(i) = Entities.Downloads.PartStatus.Complete Then clsFile.PartsCompleted += 1
                                Next
                            End If
                            clsFile.Directory = clsFI.DirectoryName
                            clsFile.LastChanged = clsMD.FileSHA512LastChanged
                            clsFile.Name = clsFI.Name
                            clsFile.Priority = Entities.Priority.Normal
                            clsFile.SHA512Hash = clsMD.FileSHA512Hash
                            clsFile.Size = clsMD.FileSHA512FileSize
                            clsFile.Started = Now
                        End If
                        If clsMD.FileIsComplete Then
                            If statusForNewFilesFound = Entities.Downloads.Status.Waiting Then
                                clsFile.Status = Entities.Downloads.Status.PendingComplete
                            Else
                                clsFile.Status = statusForNewFilesFound
                            End If
                        Else
                            clsFile.Status = statusForNewFilesFound
                        End If

                        Dim clsItem As Entities.Downloads.Item
                        clsItem = EntityHandlers.DownloadsItems.GetById(clsMD.ItemId)
                        If clsItem Is Nothing Then
                            clsItem = EntityHandlers.DownloadsItems.GetByUniqueId(clsMD.ItemUniqueId)
                        End If
                        If clsItem Is Nothing Then
                            clsItem = New Entities.Downloads.Item
                            clsItem.ItemType = clsMD.ItemType
                            clsItem.Name = clsMD.ItemName
                            clsItem.Priority = Entities.Priority.Normal
                            clsItem.UniqueId = clsMD.ItemUniqueId
                        End If

                        Using t As New TransactionScope
                            EntityHandlers.DownloadsItems.Save(clsItem, t)
                            clsFile.ItemId = clsItem.Id
                            EntityHandlers.DownloadsFiles.Save(clsFile, t)
                            t.Complete()
                        End Using
                        DownloadQueue.AddNewItems(clsItem, clsFile)
                    End SyncLock
                Else
                    SyncLock mSync
                        'ok, no metadata - try to find the file by filename, size and timestamp
                        Dim clsFile As Entities.Downloads.File
                        clsFile = EntityHandlers.DownloadsFiles.GetByNameSizeAndLastChanged(clsFI.Name, clsFI.Length, clsFI.LastWriteTimeUtc)
                        If clsFile IsNot Nothing Then
                            clsFile.Directory = clsFI.DirectoryName
                            clsFile.Status = statusForNewFilesFound
                            EntityHandlers.DownloadsFiles.Save(clsFile)
                            Dim clsItem As Entities.Downloads.Item
                            clsItem = EntityHandlers.DownloadsItems.GetById(clsFile.ItemId)
                            DownloadQueue.AddNewItems(clsItem, clsFile)
                        Else
                            'ok, no file found - no metadata... just add the file
                            'as an unconnected file and rehash - unless new status
                            'is waiting, in that case just ignore file
                            clsFile = New Entities.Downloads.File
                            clsFile.Directory = clsFI.DirectoryName
                            clsFile.Name = clsFI.Name
                            clsFile.Completed = Now
                            clsFile.LastChanged = clsFI.LastWriteTimeUtc
                            clsFile.PartCount = TransferEngine.Manager.CalculatePartCountEx(clsFI.Length)
                            clsFile.PartsCompleted = clsFile.PartCount
                            clsFile.PartStatuses = Nothing
                            clsFile.Priority = Entities.Priority.Normal
                            clsFile.SHA512Hash = String.Empty
                            clsFile.Size = clsFI.Length
                            clsFile.Started = Now
                            Select Case statusForNewFilesFound
                                Case Entities.Downloads.Status.Complete
                                    clsFile.Status = Entities.Downloads.Status.RehashCompleteFile
                                Case Entities.Downloads.Status.Recycled
                                    clsFile.Status = Entities.Downloads.Status.RehashRecycledFile
                                Case Else
                                    clsFile = Nothing
                            End Select
                            If clsFile IsNot Nothing Then
                                EntityHandlers.DownloadsFiles.Save(clsFile)
                                DownloadQueue.AddNewItems(Nothing, clsFile)
                            End If
                        End If
                    End SyncLock
                End If
            Next
        End Sub

        Public Shared Function HandleAnyFinishedDownloadedPart() As Entities.Downloads.File
            Dim clsFile As Entities.Downloads.File = Nothing
            If mFinishedParts.Count > 0 Then
                Dim clsPart As DownloadedPart = mFinishedParts.Peek
                Dim clsActivePart As ActivePart = DownloadQueue.FindActivePartByMessageId(clsPart.InReplyToMessageId)
                If clsActivePart IsNot Nothing Then
                    clsFile = EntityHandlers.DownloadsFiles.GetById(clsActivePart.FileId)
                    If clsPart.Tag = "PART UNAVAILABLE" Then
                        DownloadQueue.RemoveAvailability(clsPart.Node, clsFile.SHA512Hash, clsFile.Size, clsActivePart.PartId)
                        DownloadQueue.RemoveActivePart(clsActivePart)
                    ElseIf clsPart.Crc32CheckFailed Then
                        FileShare.Engine.Manager.OnLogEvent(String.Format("Checksum mismatch of received part {0} of file {1} from client {1}!", clsActivePart.PartId, clsFile.Name, clsActivePart.Node.Name))
                        DownloadQueue.RemoveActivePart(clsActivePart)
                    Else
                        WritePartToDisk(clsActivePart, clsPart.Stream)
                        DownloadQueue.ActivePartFinished(clsActivePart)
                    End If
                End If
                mFinishedParts.Dequeue()
            End If
            Return clsFile
        End Function

        Private Shared Sub WritePartToDisk(ByVal part As ActivePart, ByVal stream As IO.Stream)
            Dim clsFile As Entities.Downloads.File = EntityHandlers.DownloadsFiles.GetById(part.FileId)
            Dim clsItem As Entities.Downloads.Item = EntityHandlers.DownloadsItems.GetById(clsFile.ItemId)
            Dim strPath As String = IO.Path.Combine(clsFile.Directory, clsFile.Name)
            Using fs As IO.FileStream = IO.File.OpenWrite(strPath)
                fs.Seek(part.PartId * TransferEngine.Manager.PartSize, IO.SeekOrigin.Begin)
                Dim bBuffer(1024 - 1) As Byte
                Dim iCount As Integer = stream.Read(bBuffer, 0, bBuffer.Count)
                While iCount > 0
                    fs.Write(bBuffer, 0, iCount)
                    iCount = stream.Read(bBuffer, 0, bBuffer.Count)
                End While
            End Using
            Handler_ADS.UpdateADSMetaData(clsItem, clsFile, False)
            With New IO.FileInfo(strPath)
                clsFile.LastChanged = .LastWriteTimeUtc
                EntityHandlers.DownloadsFiles.Save(clsFile)
            End With
        End Sub

        Friend Shared Sub QueueIncomingCompletedFilePart(ByVal part As DownloadedPart)
            mFinishedParts.Enqueue(part)
        End Sub
        Private Shared mFinishedParts As New Generic.Queue(Of DownloadedPart)

        Public Shared Sub UpdateFileStatus(ByVal lastFile As Entities.Downloads.File, ByVal newFile As Entities.Downloads.File)
            SyncLock mSync
                If lastFile Is Nothing AndAlso newFile Is Nothing Then
                    Return
                End If
                If lastFile Is newFile Then
                    Return
                End If
                If lastFile IsNot Nothing Then
                    If DownloadQueue.IsFileActive(lastFile.Id) Then
                        lastFile.Status = Entities.Downloads.Status.Downloading
                    ElseIf lastFile.PartCount <> lastFile.PartsCompleted Then
                        lastFile.Status = Entities.Downloads.Status.Waiting
                    End If
                    EntityHandlers.DownloadsFiles.Save(lastFile)
                End If
                If newFile IsNot Nothing Then
                    newFile.Status = Entities.Downloads.Status.Downloading
                    EntityHandlers.DownloadsFiles.Save(newFile)
                End If
            End SyncLock
        End Sub
    End Class

End Namespace